/*-------------------------------------------------------------------------
 *
 * nbtxlog.h
 *	  postgres btree xlog 例程的头文件
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/nbtxlog.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef NBTXLOG_H
#define NBTXLOG_H

#include "access/transam.h"
#include "access/xlogreader.h"
#include "lib/stringinfo.h"
#include "storage/off.h"

/*
 * 适用于 btree 操作的 XLOG 记录
 *
 * XLOG 允许在日志记录的高 4 位 xl_info 字段中
 * 存储一些信息。
 */
#define XLOG_BTREE_INSERT_LEAF	0x00	/* 添加索引元组而不进行拆分 */
#define XLOG_BTREE_INSERT_UPPER 0x10	/* 同上，在非叶子页面上 */
#define XLOG_BTREE_INSERT_META	0x20	/* 同上，外加更新元数据页面 */
#define XLOG_BTREE_SPLIT_L		0x30	/* 添加拆分的索引元组 */
#define XLOG_BTREE_SPLIT_R		0x40	/* 同上，右侧的新项 */
#define XLOG_BTREE_INSERT_POST	0x50	/* 添加带有发布拆分的索引元组 */
#define XLOG_BTREE_DEDUP		0x60	/* 对页面进行元组去重 */
#define XLOG_BTREE_DELETE		0x70	/* 删除页面的叶子索引元组 */
#define XLOG_BTREE_UNLINK_PAGE	0x80	/* 删除半死的页面 */
#define XLOG_BTREE_UNLINK_PAGE_META 0x90	/* 同上，并更新元数据页面 */
#define XLOG_BTREE_NEWROOT		0xA0	/* 新根页面 */
#define XLOG_BTREE_MARK_PAGE_HALFDEAD 0xB0	/* 将叶子标记为半死 */
#define XLOG_BTREE_VACUUM		0xC0	/* 在页面上删除条目以进行
										 * 垃圾回收 */
#define XLOG_BTREE_REUSE_PAGE	0xD0	/* 旧页面即将被
										 * FSM 重新使用 */
#define XLOG_BTREE_META_CLEANUP	0xE0	/* 在元数据页面中更新清理相关数据 */

/*
 * 重建元数据页面所需的所有信息
 */
typedef struct xl_btree_metadata
{
	uint32		version;
	BlockNumber root;
	uint32		level;
	BlockNumber fastroot;
	uint32		fastlevel;
	uint32		last_cleanup_num_delpages;
	bool		allequalimage;
} xl_btree_metadata;

/*
 * 这是我们需要知道的关于简单（无拆分）插入的信息。
 *
 * 此数据记录用于 INSERT_LEAF、INSERT_UPPER、INSERT_META 和
 * INSERT_POST。请注意，INSERT_META 和 INSERT_UPPER 暗示它不是
 * 叶子页面，而 INSERT_POST 和 INSERT_LEAF 暗示它必须是叶子
 * 页面。
 *
 * 备份块 0：原始页面
 * 备份块 1：子节点的左侧兄弟，如果 INSERT_UPPER 或 INSERT_META
 * 备份块 2：xl_btree_metadata，如果 INSERT_META
 *
 * 注意：在发布列表拆分插入的情况下（即 INSERT_POST 情况），
 * 新元组实际上是“原始”新项。发布列表的拆分偏移量
 * 在原始新项之前记录。恢复需要这两个信息，因为
 * 它必须对作为额外步骤拆分的现有发布列表进行原地
 * 更新。此外，恢复生成一个“最终”的新项。有关
 * 发布列表拆分的详细信息，请参见 _bt_swap_posting()。
 */
typedef struct xl_btree_insert
{
	OffsetNumber offnum;

	/* 发布拆分偏移量紧随其后（INSERT_POST 情况） */
	/* 新元组总是位于最后 */
} xl_btree_insert;

#define SizeOfBtreeInsert	(offsetof(xl_btree_insert, offnum) + sizeof(OffsetNumber))


/*
 * 在拆分插入时，我们保存所有进入右兄弟节点的项
 * 以便能够从日志记录中完全恢复。这种方式占用的
 * xlog 空间比正常方法少，因为如果我们按标准处理，
 * XLogInsert几乎总是认为右页是新的，并存储其
 * 整个页面图像。然而，左页则按照正常的
 * 增量更新方式进行处理。
 *
 * 注意：XLOG_BTREE_SPLIT_L 和 XLOG_BTREE_SPLIT_R 共享此数据记录。
 * 有两种变体用于指示插入的元组是进入左侧还是右侧拆分页
 * （因此，新的项目是否被存储）。我们总是记录左页的高键，
 * 因为后缀截断可以使用用户定义的代码生成一个新的叶节点高键。
 * 这在内部页面上也是必要的，因为左页的高键所基于的
 * firstright 项目在右页中将会被截断为零属性（分隔符键
 * 在右页中不可用）。
 *
 * 备份块 0：原始页面 / 新左页
 *
 * 左页的数据部分包含新的项目，如果它是 _L 变体。
 * _R 变体的拆分记录通常没有 newitem （_R 变体的叶节点
 * 页拆分记录在处理发布列表拆分时会包括一个显式的 newitem，
 * 尽管在右页中从未使用 -- 它实际上是一个 orignewitem，
 * 用于更新现有的发布列表）。左页/原始页面的新高键
 * 位于最后一项（并且必须始终存在）。
 *
 * 需要 REDO 例程直接处理发布列表拆分的页面拆分记录
 * 将有一个显式的 newitem，实际上是一个 orignewitem
 * （即在发布列表拆分之前的 newitem，而不是之后）。发布列表拆分
 * 始终会有一个 newitem，它紧接在被拆分的发布列表后面
 * （在拆分之前会与 orignewitem 重叠）。通常，REDO 必须处理
 * 一个 _L 变体的页面拆分记录，并且通常，新的发布列表
 * 和最终的新项都放在左页上（现有的发布列表将被插入
 * 代替旧的，最终的新项将被插入到其旁边）。然而，
 * 当页面的拆分点恰好有一个 lastleft 元组，且该元组
 * 也是被拆分的发布列表时，_R 变体的拆分记录将
 * 包括一个 orignewitem （将 newitem 留作页面拆分的
 * firstright 元组）。这一边角情况的存在并不改变
 * REDO 例程中 newitem/orignewitem 的基本事实：
 * 它始终是专用于左页的状态。（这就是为什么记录的
 * postingoff 字段不是发生发布列表拆分的可靠指示符；
 * 非零值仅表示 REDO 例程必须重建一个新的发布列表元组，
 * 这是左页所需的。）
 *
 * 这种发布列表拆分处理相当于 xl_btree_insert REDO
 * 例程的 INSERT_POST 处理。尽管细节在这里更复杂，
 * 概念和目标完全相同。有关发布列表拆分的详细信息，
 * 请参见 _bt_swap_posting()。
 *
 * 备份块 1：新右页
 *
 * 右页的数据部分包含右页的元组，以 _bt_restore_page 使用的
 * 形式。这包括新的项目，如果它是 _R 变体。
 * 右页的元组还包含右页的高键（来自左侧/原始页面
 * 在拆分时移动），除非该拆分恰好是其级别上最右侧页面的拆分，
 * 在这种情况下新右页没有高键。
 *
 * 备份块 2：下一个块（原始页面的 rightlink），如果有的话
 * 备份块 3：子节点的左兄弟，如果是非叶节点拆分
 */
typedef struct xl_btree_split
{
	uint32		level;			/* 被拆分页面的树级别 */
	OffsetNumber firstrightoff; /* 右页上的第一个原始页面项 */
	OffsetNumber newitemoff;	/* 新项目的偏移量 */
	uint16		postingoff;		/* 原始发布元组内部的偏移量 */
} xl_btree_split;

#define SizeOfBtreeSplit	(offsetof(xl_btree_split, postingoff) + sizeof(uint16))

/*
 * 当页面去重时，具有相等键的连续元组组被合并成发布列表元组。
 *
 * WAL记录表示叶页面的去重过程。后面跟着一个BTDedupInterval结构体数组。
 */
typedef struct xl_btree_dedup
{
	uint16		nintervals;

	/* 去重间隔跟随 */
} xl_btree_dedup;

#define SizeOfBtreeDedup 	(offsetof(xl_btree_dedup, nintervals) + sizeof(uint16))

/*
 * 这是我们需要知道的关于btree中页面重用的信息。此记录仅用于生成热备份的冲突点。
 *
 * 请注意，我们必须在记录中包含一个RelFileNode，因为我们实际上并没有
 * 将缓冲区与记录关联起来。
 */
typedef struct xl_btree_reuse_page
{
	RelFileNode node;
	BlockNumber block;
	FullTransactionId latestRemovedFullXid;
} xl_btree_reuse_page;

#define SizeOfBtreeReusePage	(sizeof(xl_btree_reuse_page))

/*
 * xl_btree_vacuum和xl_btree_delete记录描述了叶页面上索引
 * 元组的删除。前者变体由VACUUM使用，而后者变体用于
 * 在调用btinsert()时有时发生的临时删除。
 *
 * 这些记录非常相似。唯一的区别是xl_btree_delete
 * 必须包含一个latestRemovedXid字段，以生成恢复冲突。
 * （VACUUM操作可以依靠在截断指向要删除的索引元组的表时生成的早期冲突。
 * 每个REDO例程之间也存在小差异，我们在这里不深入讨论。）
 *
 * xl_btree_vacuum和xl_btree_delete都表示在单个叶页面上使用页偏移量
 * 数字删除任意数量的索引元组。两者还支持“更新”索引元组，这就是如何实现
 * 删除现有发布列表元组中包含的部分TID的方式。
 *
 * 更新的发布列表元组使用xl_btree_update元数据表示。
 * REDO例程各自使用xl_btree_update条目（加上目标叶页面中每个
 * 相应的原始索引元组）生成最终的更新元组。
 *
 * 更新仅在REDO例程将留下某些剩余TID时使用。否则，发布列表元组将直接被删除。
 */
typedef struct xl_btree_vacuum
{
	uint16		ndeleted;
	uint16		nupdated;

	/* 被删除的目标偏移量数字跟随 */
	/* 更新的目标偏移量数字跟随 */
	/* 更新的元组元数据(xl_btree_update)数组跟随 */
} xl_btree_vacuum;

#define SizeOfBtreeVacuum	(offsetof(xl_btree_vacuum, nupdated) + sizeof(uint16))

typedef struct xl_btree_delete
{
	TransactionId latestRemovedXid;
	uint16		ndeleted;
	uint16		nupdated;

	/* 被删除的目标偏移量数字跟随 */
	/* 更新的目标偏移量数字跟随 */
	/* 更新的元组元数据(xl_btree_update)数组跟随 */
} xl_btree_delete;

#define SizeOfBtreeDelete	(offsetof(xl_btree_delete, nupdated) + sizeof(uint16))

/*
 * 在xl_btree_update元数据中出现的偏移量是元组的
 * 原始发布列表中的偏移量，而不是页面偏移量数字。这些是
 * 基于0的。原始发布列表元组的页面偏移量数字来自主xl_btree_vacuum/xl_btree_delete记录。
 */
typedef struct xl_btree_update
{
	uint16		ndeletedtids;

	/* 指向被删除TID的发布列表 uint16 偏移量跟随 */
} xl_btree_update;

#define SizeOfBtreeUpdate	(offsetof(xl_btree_update, ndeletedtids) + sizeof(uint16))

/*
 * 这是我们需要知道的关于标记空子树以供删除的信息。
 * 目标标识从父页面移除的元组（请注意，我们
 * 移除了该元组的下链和*后续*元组的键）。请注意
 * 叶页面是空的，因此我们不需要存储其内容——它在恢复过程中仅使用
 * 其余字段重新初始化。
 *
 * 备份块 0： 叶块
 * 备份块 1： 顶级父节点
 */
typedef struct xl_btree_mark_page_halfdead
{
	OffsetNumber poffset;		/* 父页面中被删除的元组ID */

	/* 重新创建叶页面所需的信息： */
	BlockNumber leafblk;		/* 最终被删除的叶块 */
	BlockNumber leftblk;		/* 叶块的左侧兄弟，如果有的话 */
	BlockNumber rightblk;		/* 叶块的右侧兄弟 */
	BlockNumber topparent;		/* 子树中最上面的内部页面 */
} xl_btree_mark_page_halfdead;

#define SizeOfBtreeMarkPageHalfDead (offsetof(xl_btree_mark_page_halfdead, topparent) + sizeof(BlockNumber))

/*
 * 这是我们需要知道的关于删除btree页面的信息。请注意，我们
 * 仅在删除页面中留下少量的管理信息（删除页面必须保留一段时间
 * 作为墓碑）。对于REDO例程从头开始重新生成其目标页面是便利的。
 * 这就是WAL记录描述某些细节的原因，这些细节实际上直接
 * 可从目标页面获得。
 *
 * 备份块 0： 要删除的目标块
 * 备份块 1： 目标块的左侧兄弟，如果有的话
 * 备份块 2： 目标块的右侧兄弟
 * 备份块 3： 叶块（如果不同于目标）
 * 备份块 4： 元数据页（如果右侧兄弟成为新的快速根）
 */
typedef struct xl_btree_unlink_page
{
	BlockNumber leftsib;		/* 目标块的左侧兄弟，如果有的话 */
	BlockNumber rightsib;		/* 目标块的右侧兄弟 */
	uint32		level;			/* 目标块的级别 */
	FullTransactionId safexid;	/* 目标块的BTPageSetDeleted() XID */

	/*
	 * 重新创建带有正确的topparent链接的半死叶页面所需的信息。
	 * 这些字段仅在删除操作的目标页面是内部页面时使用。
	 * REDO例程从头开始创建半死页面，以保持事情简单（这与
	 * 目标页面自身所使用的便利方法相同）。
	 */
	BlockNumber leafleftsib;
	BlockNumber leafrightsib;
	BlockNumber leaftopparent;	/* 子树中下一个孩子 */

	/* 如果XLOG_BTREE_UNLINK_PAGE_META，则跟随xl_btree_metadata */
} xl_btree_unlink_page;

#define SizeOfBtreeUnlinkPage	(offsetof(xl_btree_unlink_page, leaftopparent) + sizeof(BlockNumber))


/*
 * 新的根日志记录。如果这是为了建立一个空根，则元组为零；如果是旧根分裂的结果，则元组为两个。
 *
 * 注意，虽然这意味着需要重写元数据页面，但我们不需要 xl_btree_metadata 记录——rootblk 和 level 就足够了。
 *
 * 备份块 0：新根页面（如果分裂旧根，则有效负载为 2 个元组）
 * 备份块 1：左子节点（如果分裂旧根）
 * 备份块 2：元数据页面
 */
typedef struct xl_btree_newroot
{
	BlockNumber rootblk;		/* 新根的位置（与块 0 冗余） */
	uint32		level;			/* 其树级别 */
} xl_btree_newroot;

#define SizeOfBtreeNewroot	(offsetof(xl_btree_newroot, level) + sizeof(uint32))


/*
 * nbtxlog.c 中函数的原型
 */
extern void btree_redo(XLogReaderState *record);
extern void btree_desc(StringInfo buf, XLogReaderState *record);
extern const char *btree_identify(uint8 info);
extern void btree_xlog_startup(void);
extern void btree_xlog_cleanup(void);
extern void btree_mask(char *pagedata, BlockNumber blkno);

#endif							/* NBTXLOG_H */
